package com.hdu.manager;

import com.alibaba.fastjson.JSON;
import com.hdu.annotation.DelayTask;
import com.hdu.config.DelayTaskClientProperties;
import com.hdu.holder.BeanAndMethodHolder;
import com.hdu.result.R;
import com.hdu.util.ApplicationContextHolder;
import com.hdu.util.SystemEnvironment;
import com.hdu.util.HttpUtils;

import lombok.extern.slf4j.Slf4j;
import lombok.val;

import org.springframework.beans.factory.SmartInitializingSingleton;



import java.io.IOException;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;


import static com.hdu.util.ReflectionUtils.getMethodsByTargetAnnotation;

@Slf4j
public class DelayTaskManager implements SmartInitializingSingleton {

    private final Map<String, BeanAndMethodHolder> TASK_NAME_2_BEAN_METHOD_HOLDER = new HashMap<>();
    private final DelayTaskClientProperties delayTaskClientProperties;

    private final static String REGISTER_URL = "/client/register";

    public DelayTaskManager(DelayTaskClientProperties delayTaskClientProperties) {
        this.delayTaskClientProperties = delayTaskClientProperties;
    }

    @Override
    public void afterSingletonsInstantiated() {
        val allBeans = ApplicationContextHolder.getBeansOfType(Object.class);
        allBeans.values().forEach(this::collectDelayTaskMethods);
        registerServiceInstance();
    }

    private void registerServiceInstance() {
        log.info("Register delay task info: {}", TASK_NAME_2_BEAN_METHOD_HOLDER);

        val taskName2ServiceInstance =
                TASK_NAME_2_BEAN_METHOD_HOLDER.keySet().stream()
                        .collect(Collectors.toMap(
                                Function.identity(),
                                taskName -> SystemEnvironment.getInstance()
                        ));

        try {
            val res = HttpUtils.doPost(
                    delayTaskClientProperties.getDelayTaskCenterUrl() + REGISTER_URL,
                    JSON.toJSONString(taskName2ServiceInstance)
            );
            val r = JSON.parseObject(res, R.class);
            if (r.isFail()) {
                log.error("register error, taskName2ServiceInstance : {}, msg : {}", taskName2ServiceInstance, r.getMsg());
            }
        } catch (IOException e) {
            log.error("register error, taskName2ServiceInstance : {}", taskName2ServiceInstance);
        }
    }

    private void collectDelayTaskMethods(Object bean) {
        val methods = getMethodsByTargetAnnotation(DelayTask.class, bean.getClass());
        if (methods.isEmpty()) {
            return;
        }
        methods.forEach(m -> {
            val dT = m.getAnnotation(DelayTask.class);
            if (TASK_NAME_2_BEAN_METHOD_HOLDER.containsKey(dT.taskName())) {
                log.error("Duplicate task name: {}", dT.taskName());
                throw new IllegalArgumentException("Duplicate task name: " + dT.taskName());
            }
            TASK_NAME_2_BEAN_METHOD_HOLDER.put(
                    dT.taskName(),
                    new BeanAndMethodHolder(bean, m)
            );
        });
    }

    public R trigger(String taskName, String taskParam) {
        val beanAndMethodHolder = TASK_NAME_2_BEAN_METHOD_HOLDER.get(taskName);
        if (beanAndMethodHolder == null) {
            return R.fail("No such task: " + taskName);
        }
        try {
            return beanAndMethodHolder.invoke(taskName, taskParam);
        } catch (Exception e) {
            log.error("Trigger delay task failed: {}", e.getMessage());
            return R.fail("Trigger delay task failed: " + e.getMessage());
        }
    }
}
